/* Ian Hendry, mailto:ianhendry@yahoo.com */

package apidemo;

//import java.awt.event.ActionEvent;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import apidemo.AccountInfoPanel.PortfolioModel;
import apidemo.OrdersPanel.OrderRow;
import apidemo.OrdersPanel.OrdersModel;
import apidemo.util.HtmlButton;
import apidemo.util.VerticalPanel;
import apidemo.util.NewTabbedPanel.INewTab;
import apidemo.util.VerticalPanel.HorzPanel;

import com.ib.client.Order;
import com.ib.controller.ApiController.IOrderHandler;
import com.ib.controller.NewContract;
import com.ib.controller.NewOrder;
import com.ib.controller.NewOrderState;
import com.ib.controller.OrderStatus;
import com.ib.controller.Position;
import com.ib.controller.ApiController.IAccountHandler;
import com.ib.controller.Types.SecType;
import com.sun.org.apache.bcel.internal.generic.IINC;

public class ClosePositionPanel extends HorzPanel implements INewTab, IAccountHandler {
	/**
	 * 
	 */
	private static final long serialVersionUID = 7908514325600386914L;
	private DefaultListModel m_acctList = new DefaultListModel();
	private JList m_accounts = new JList(m_acctList);
	private OrdersModel m_model = new OrdersModel();
	private String m_selAcct = "";
	private PortfolioModel m_portfolioModel = new PortfolioModel();
	private JTable m_portTable = new JTable(m_portfolioModel);
	private final static int fONE_DAY = 1;

	ClosePositionPanel() {
		JScrollPane acctsScroll = new JScrollPane(m_accounts);
		acctsScroll.setBorder(new TitledBorder("Select account"));

		JScrollPane portScroll = new JScrollPane(m_portTable);
		portScroll.setBorder(new TitledBorder("Select position"));

		setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
		add(acctsScroll);
		add(portScroll);
		add(new ExPanel());
		
		m_accounts.addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent e) {
				onAcctChanged();
			}
		});
	}
	
	class ExPanel extends VerticalPanel {

		ExPanel() {

			final JButton refreshPositions = new JButton("Refresh Positions List");
			refreshPositions.addActionListener(new ActionListener() {
			    @Override
			    public void actionPerformed(ActionEvent e) { 
			        // Take somehow care of multiple clicks
			    	onPositionRefresh();
			    }
			});
			
			JButton closeNowButton = new JButton("Close Position now");
			closeNowButton.addActionListener(new ActionListener() {
			    @Override
			    public void actionPerformed(ActionEvent e) { 
			        // Take somehow care of multiple clicks
			    	onClosePositionNow();
			    }
			});
			
			final JCheckBox addDayCheckBox = new JCheckBox("Close tomorrow?");
			
			final JTextField closeTimeTextField = new JTextField(20);
			closeTimeTextField.setText("04:45");
			
			final JButton scheduleButton = new JButton("Close Position schedule");
			scheduleButton.addActionListener(new ActionListener() {
			    @Override
			    public void actionPerformed(ActionEvent e) { 
			        // Take somehow care of multiple clicks
			    	final Position position = getPositionToClose();
			    	if (position != null) {
			    		scheduleButton.setText("Closing...");
				        boolean addaDay = false; 
				        //TODO validate this is in a time format not some garbage
				        String text = closeTimeTextField.getText();
				        if(addDayCheckBox.isSelected() && !addDayCheckBox.getText().isEmpty()) {
				        	addaDay = true;
				        }
				        int timeDely = getSecondsUntilClose(text, addaDay);
				        final Timer t = new Timer(timeDely, new ActionListener() {
				            @Override
				            public void actionPerformed(ActionEvent evt) {
				            	onClosePosition(position);
				            	scheduleButton.setText("Close Positions");
				            }
				        });
				        t.setRepeats(false);
				        t.start();
			    	}
			    }
			});
			// add items to frame
			add(refreshPositions);
			add(closeNowButton);
			add(scheduleButton);
			add(closeTimeTextField);
			add(addDayCheckBox);
		}
	}

	protected void onAcctChanged() {
		int i = m_accounts.getSelectedIndex();
		if (i != -1) {
			String selAcct = (String) m_acctList.get(i);
			if (!selAcct.equals(m_selAcct)) {
				m_selAcct = selAcct;
				m_portfolioModel.clear();
				ApiDemo.INSTANCE.controller().reqAccountUpdates(true, m_selAcct, this);
			}
		}
	}

	protected void onPositionRefresh() {
//		int i = m_accounts.getSelectedIndex();
//		if (i != -1) {
//			String selAcct = (String) m_acctList.get(i);
//			if (selAcct.equals(m_selAcct)) {
//				m_selAcct = selAcct;
				m_portfolioModel.clear();
				ApiDemo.INSTANCE.controller().reqAccountUpdates(true, m_selAcct, this);
//			} else {
//				ApiDemo.INSTANCE.show("Please select an account");
//			}
//		}
	}
	
	private static int getSecondsUntilClose(String theTime, boolean addaDay) {
		
		Date _final = getTimeToClose(theTime, addaDay);
		Date _initial =  new Date();
		
		int seconds = (int) (_final.getTime() - _initial.getTime());
		ApiDemo.INSTANCE.show("Positions scheduled to closed at " + _final.toString());
		return seconds;
	}
	
	private static Date getTimeToClose(String theTime, boolean addaDay) {
		int hourPart = Integer.parseInt(theTime.substring(0, theTime.indexOf(":")));
		int minutePart = Integer.parseInt(theTime.substring(theTime.indexOf(":")+1, theTime.length()));
		
		Calendar today = new GregorianCalendar();
		if (addaDay) {
			today.add(Calendar.DATE, fONE_DAY);
		}
		Calendar result = new GregorianCalendar(today.get(Calendar.YEAR),
				today.get(Calendar.MONTH), today.get(Calendar.DATE), hourPart,
				minutePart);
		System.out.println("The job will be called at " + result.getTime());
		return result.getTime();
	}
	
	private Position getPositionToClose(){
		Position position = null;
		String account = (String) m_accounts.getSelectedValue();
		int i = m_portTable.getSelectedRow();
		if (i != -1) {
			if (account != null) {
				position = m_portfolioModel.getPosition(i);
			}
		} else {
			// no position selected error
			ApiDemo.INSTANCE.show("WARNING: Please select a position to close");
		}
		return position;
	}
	
	protected void onClosePosition(Position position) {
		
		// we want be be sure that the position is the same as when we clicked the button
		// i.e. it didn't hit TP or SL in the mean time
		ApiDemo.INSTANCE.controller().reqAccountUpdates(true, m_selAcct, this);
		Position currentPosition = m_portfolioModel.getPosition(0);
		
		if ((position.contract().secType() == SecType.FUT) && (currentPosition.position() != 0)) {
	
			//Build the order from the current position
			Order order = buildOrder(position);
			NewContract order_ticker = position.contract();
			
			// place the order through the API, the function return the number of open positions after order executed which should == zero
			int errorCode = placeOrder(order, order_ticker);
			if (errorCode != 0) {
				// an error has occurred and a positions is open
				ApiDemo.INSTANCE.show("ERROR: THERE ARE POSITIONS OPEN - PLEASE CHECK LOG AND CLOSE MANUALLY");
			} else {
				// all good report successful close of position
				ApiDemo.INSTANCE.show("SUCCESS. " + "There are " + errorCode + " positions open.");
			}
			
			// finally cancel any residual stop loss or profit limit orders
			ApiDemo.INSTANCE.show("Finally we attempt to close Stop Loss and Take Profit orders.");
			closeStopLossAndTakeProfitOrders();
			
		} else {
			// nothing to do = no open positions on any future contracts
			ApiDemo.INSTANCE.show("WARNING: Nothing to do - no open positions on any future contracts");
		}
	}
	
	private void closeStopLossAndTakeProfitOrders() {
		
		ApiDemo.INSTANCE.show("Cancel all orders called at: " + new Date());
		
		m_model.clear();
		//m_model.fireTableDataChanged();
		ApiDemo.INSTANCE.controller().takeTwsOrders( m_model);
		
		for (int i=0; i<m_model.getRowCount(); i++){
			OrderRow order = m_model.get(i);
			if (order != null) {
				ApiDemo.INSTANCE.controller().cancelOrder( order.m_order.orderId() );
			}
		}
		
		//ApiDemo.INSTANCE.show("Cancel all orders completed at: " + new Date());
	}
	
	protected void onClosePositionNow() {
		String account = (String) m_accounts.getSelectedValue();
		
		int i = m_portTable.getSelectedRow();
		if (i != -1) {
			if (account != null) {
				Position position = m_portfolioModel.getPosition(i);

				// only call the close orders on futures contracts with open positions
				if ((position.contract().secType() == SecType.FUT)
						&& (position.position() != 0)) {

					Order order = buildOrder(position);
					NewContract order_ticker = position.contract();
					
					int errorCode = placeOrder(order, order_ticker);
					if (errorCode != 0) {
						// an error has occurred and a positions is open
						ApiDemo.INSTANCE.show("ERROR: THERE ARE POSITIONS OPEN - PLEASE CHECK LOG AND CLOSE MANUALLY");
					} else {
						// all good report successful close of position
						ApiDemo.INSTANCE.show("SUCCESS. " + "There are " + errorCode + " positions open.");
					}
					
					// finally cancel any residual stop loss or profit limit orders
					ApiDemo.INSTANCE.show("Finally we attempt to close Stop Loss and Take Profit orders.");
					closeStopLossAndTakeProfitOrders();

				} else {
					// nothing to do = no open positions on any future contracts
					ApiDemo.INSTANCE.show("WARNING: Nothing to do - no open positions on any future contracts");
				}
			}
		} else {
			// no position selected error
			ApiDemo.INSTANCE.show("WARNING: Please select a position to close");
		}
		
	}

	private Order buildOrder(Position position) {

		// Create an order object
		Order order = new Order();

		// if we're currently long (signified by a number of contracts > 0) then we need to sell otherwise buy
		if (position.position() > 0) {
			order.m_action = "SELL";
		} else if (position.position() < 0) {
			order.m_action = "BUY";
		}
		order.m_totalQuantity = Math.abs(position.position());
		order.m_orderType = "MKT";
		order.m_transmit = true;
		order.m_referencePriceType = 0;
		order.m_orderRef = "Auto close position at 3:45am";
		order.m_orderRef = "Auto";
		order.m_tif = "DAY";
		
		return order;
		
	}
	
	private int placeOrder(Order order, NewContract order_ticker) {
		
		NewOrder newOrderExecution = new NewOrder(order);
		
		// Send the Market Order request
		ApiDemo.INSTANCE.controller().placeOrModifyOrder(order_ticker, newOrderExecution, new IOrderHandler() {

					@Override
					public void orderState(NewOrderState orderState) {
						ApiDemo.INSTANCE.show("This is the orderState: "+ orderState.status().toString());
						// Filled = 6 (ordinal)
						if (orderState.status().ordinal() == 6) {
							ApiDemo.INSTANCE.show("Positions closed at " + new Date());
						} else {
							//doAlarmSignal();
							ApiDemo.INSTANCE.show("(orderState) TEST ALARM FIRED: ORDER STATE IS " + orderState.status().toString());
						}
					}

					@Override
					public void handle(int errorCode, String errorMsg) {
						ApiDemo.INSTANCE.show("ERROR: An error occured for the action: "+ errorCode+ " "+ errorMsg);
						//doAlarmSignal();
						ApiDemo.INSTANCE.show("(handle) TEST ALARM FIRED AT " + new Date());
					}

					@Override
					public void orderStatus(OrderStatus status,
							int filled, int remaining,
							double avgFillPrice, long permId,
							int parentId, double lastFillPrice,
							int clientId, String whyHeld) {
						//ApiDemo.INSTANCE.show("This is the orderStatus: " + status.toString());
					}
				});
		
		// TODO improve this to wait until the orders are processed not just wait enough time to allow them to process
		try {
		    Thread.sleep(5000);                 
		} catch(InterruptedException ex) {
		    Thread.currentThread().interrupt();
		}
		
		// function is going to return the number of open positions after the oder has been executed. 
		// this should be zero as in no open positions anymore
		ApiDemo.INSTANCE.controller().reqAccountUpdates(true, m_selAcct, this);
		Position anyPositions = m_portfolioModel.getPosition(0);
		
		return anyPositions.position();
	}

	private void doAlarmSignal() {
		int count = 0; 
		do  {
			Toolkit.getDefaultToolkit().beep();
			count++;
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				System.out.println("Error in Run(); " + e.getLocalizedMessage());
				System.out.println("Error in Run(); " + e.getStackTrace());
			} 
		} while (count < 10);
	}
	 
	/** Show futures contracts positions only. */
	@Override
	public void updatePortfolio(Position position) {
		if (position.account().equals(m_selAcct)) {
			m_portfolioModel.update(position);
		}
	}

	/** Called when the tab is first visited. */
	@Override
	public void activated() {
		for (String account : ApiDemo.INSTANCE.accountList()) {
			m_acctList.addElement(account);
		}
	}

	/** Called when the tab is closed by clicking the X. */
	@Override
	public void closed() {
	}

	@Override
	public void accountValue(String account, String key, String value,
			String currency) {
	}

	@Override
	public void accountTime(String timeStamp) {
	}

	@Override
	public void accountDownloadEnd(String account) {
	}

}
